package cn.tedu.mall.seckill.service.impl;

import cn.tedu.mall.common.exception.CoolSharkServiceException;
import cn.tedu.mall.common.restful.JsonPage;
import cn.tedu.mall.common.restful.ResponseCode;
import cn.tedu.mall.pojo.product.vo.SpuDetailStandardVO;
import cn.tedu.mall.pojo.product.vo.SpuStandardVO;
import cn.tedu.mall.pojo.seckill.model.SeckillSpu;
import cn.tedu.mall.pojo.seckill.vo.SeckillSpuDetailSimpleVO;
import cn.tedu.mall.pojo.seckill.vo.SeckillSpuVO;
import cn.tedu.mall.product.service.seckill.IForSeckillSpuService;
import cn.tedu.mall.seckill.mapper.SeckillSpuMapper;
import cn.tedu.mall.seckill.service.ISeckillSpuService;
import cn.tedu.mall.seckill.utils.RedisBloomUtils;
import cn.tedu.mall.seckill.utils.SeckillCacheUtils;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.math.RandomUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class SeckillSpuServiceImpl implements ISeckillSpuService {
    @Autowired
    private SeckillSpuMapper seckillSpuMapper;
    //秒杀表spu中没有商品详情，需要根据spuId从mall_pms数据库中的spu表里面获取，所以需要Dubbo
    @DubboReference
    private IForSeckillSpuService dubboSeckillSpuService;

    //SeckillCacheUtils中没有定义Detail对应的key常量值，所以我们自己定义一个
    public static final String SECKILL_SPU_DETAIL_VO_PREFIX="seckill:spu:detail:vo:";
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedisBloomUtils redisBloomUtils;
    @Override
    public JsonPage<SeckillSpuVO> listSeckillSpus(Integer page, Integer pageSize) {
        //分页查询秒杀表中spu信息
        //设置分页信息
        PageHelper.startPage(page,pageSize);
        List<SeckillSpu> seckillSpus = seckillSpuMapper.findSeckillSpus();
        //我们需要将seckillSpus集合中的对象，转换为SeckillSpuVO类型对象才能返回
        List<SeckillSpuVO> seckillSpuVOS = new ArrayList<>();
        //遍历seckillSpus集合，将其中对象转换为SeckillSpuVO放入seckillSpuVOS集合中
        for (SeckillSpu seckillSpu : seckillSpus) {
            //根据spuId查询pms库中的spu表中的数据
            Long spuId = seckillSpu.getSpuId();
            SpuStandardVO spuStandardVO = dubboSeckillSpuService.getSpuById(spuId);
            //spuStandardVO大部分数据和SeckillSpuVO是同名属性，可以直接赋值
            SeckillSpuVO seckillSpuVO = new SeckillSpuVO();
            BeanUtils.copyProperties(spuStandardVO,seckillSpuVO);
            //将seckillSpu中的属性也赋值到seckillSpuVO对象中
            //赋值秒杀价
            seckillSpuVO.setSeckillListPrice(seckillSpu.getListPrice());
            //赋值起始时间和结束时间
            seckillSpuVO.setStartTime(seckillSpu.getStartTime());
            seckillSpuVO.setEndTime(seckillSpu.getEndTime());
            //将即包含商品信息，又包含秒杀信息的seckillSpuVO保存到集合中
            seckillSpuVOS.add(seckillSpuVO);
        }
        //返回分页结果
        return JsonPage.restPage(new PageInfo<>(seckillSpuVOS));
    }

    //根据spuId查询spu详情
    @Override
    public SeckillSpuVO getSeckillSpu(Long spuId) {
        //先判断当前spuId是否在布隆过滤器中
        //如果不在直接抛出异常
        //获取本批次的布隆过滤器的key
        String bloomTodayKey = SeckillCacheUtils.getBloomFilterKey(LocalDate.now());
        log.info("当前批次的布隆过滤器key为：{}",bloomTodayKey);
        if(!redisBloomUtils.bfexists(bloomTodayKey,spuId+"")){
            //进入这里表示不存在，防止缓存穿透，抛出异常
            throw new CoolSharkServiceException(ResponseCode.NOT_FOUND,"您访问的商品不存在(测试布隆过滤器)");
        }

        //声明返回值类型对象
        SeckillSpuVO seckillSpuVO = null;
        //获得SeckillSpuVO对象的key常量
        String seckillSpuVOKey = SeckillCacheUtils.getSeckillSpuVOKey(spuId);
        //判断Redis中是否有该key
        if(redisTemplate.hasKey(seckillSpuVOKey)){
            //有，直接返回
            seckillSpuVO = (SeckillSpuVO) redisTemplate.boundValueOps(seckillSpuVOKey).get();
        } else {
            //没有，从数据库中查询
            //需要查询两张表pms_spu、seckill_spu
            SeckillSpu seckillSpu = seckillSpuMapper.findSeckillSpuById(spuId);
            //判断spuId查询不到信息，抛出异常的情况(布隆过滤器误判时会进入这个if)
            if (seckillSpu==null)
                throw new CoolSharkServiceException(ResponseCode.NOT_FOUND,"您访问的商品不存在");
            //秒杀信息查询结束，下面查询pms_spu表中的数据
            SpuStandardVO spuStandardVO = dubboSeckillSpuService.getSpuById(spuId);
            //数据赋值到seckillSpuVO
            seckillSpuVO = new SeckillSpuVO();
            BeanUtils.copyProperties(spuStandardVO,seckillSpuVO);
            //秒杀信息赋值
            seckillSpuVO.setSeckillListPrice(seckillSpu.getListPrice());
            seckillSpuVO.setStartTime(seckillSpu.getStartTime());
            seckillSpuVO.setEndTime(seckillSpu.getEndTime());
            //将赋值好的seckillSpuVO对象保存在Redis中
            redisTemplate.boundValueOps(seckillSpuVOKey).set(seckillSpuVO,
                    24*60*60+RandomUtils.nextInt(100),TimeUnit.SECONDS);
        }
        //判断当前时间是否在秒杀时间段内
        //为了提高效率，不再连接到数据库
        LocalDateTime nowTime = LocalDateTime.now();
        //看当前时间是否大于开始时间 并且 小于结束时间
        //时间对象计算时间差Duration
        //时间差为负值就会返回negative
        //判断当前时间大于开始时间
        Duration afterTime = Duration.between(nowTime, seckillSpuVO.getStartTime());
        //判断结束时间大于当前时间
        Duration beforeTime = Duration.between(seckillSpuVO.getEndTime(), nowTime);
        //如果afterTime和beforeTime都是negative，证明当前时间大于开始时间并且小于结束时间
        //可以访问，需要将随机码返回给前端
        if(afterTime.isNegative() && beforeTime.isNegative()){
            //从redis中获取随机码，赋值到seckillSpuVO对象的url属性中
            String randCodeKey = SeckillCacheUtils.getRandCodeKey(spuId);
            int randomCode = (int) redisTemplate.boundValueOps(randCodeKey).get();
            seckillSpuVO.setUrl("/seckill/"+randomCode);
        }
        //返回对象
        //返回的seckillSpuVO对象中包含了Url以及随机码，这个随机码会返回给前端保存
        //前端只有利用这个url发起的购买请求，才会给控制器接受处理
        return seckillSpuVO;
    }

    //根据spuId查询detail详情信息
    @Override
    public SeckillSpuDetailSimpleVO getSeckillSpuDetail(Long spuId) {
        //常量后面添加spuId
        String seckillDetailKey = SECKILL_SPU_DETAIL_VO_PREFIX+ spuId;
        //声明当前方法规定的返回值对象
        SeckillSpuDetailSimpleVO seckillSpuDetailSimpleVO = null;
        //判断redis中是否已经包含这个对象
        if(redisTemplate.hasKey(seckillDetailKey)){
            //有，直接返回
            seckillSpuDetailSimpleVO =
                    (SeckillSpuDetailSimpleVO) redisTemplate.boundValueOps(seckillDetailKey).get();
        } else {
            //没有，查询数据库
            SpuDetailStandardVO spuDetailStandardVO = dubboSeckillSpuService.getSpuDetailById(spuId);
            //将spuDetailStandardVO对象中的同名属性赋值给seckillSpuDetailSimpleVO
            seckillSpuDetailSimpleVO = new SeckillSpuDetailSimpleVO();
            BeanUtils.copyProperties(spuDetailStandardVO,seckillSpuDetailSimpleVO);
            //将当前赋值完成的对象保存到Redis中
            redisTemplate.boundValueOps(seckillDetailKey).set(seckillSpuDetailSimpleVO,
                    24*60*60+ RandomUtils.nextInt(100), TimeUnit.SECONDS);
        }
        //最后返回值
        return seckillSpuDetailSimpleVO;
    }
}
